/**
 * 
 */
package photosalvage.rs.photos;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.validation.ValidationException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriBuilder;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import photosalvage.PhotoSalvageConstants;
import photosalvage.entity.Album;
import photosalvage.entity.AlbumRepository;
import photosalvage.entity.Owner;
import photosalvage.entity.OwnerRepository;
import photosalvage.entity.PagedList;
import photosalvage.entity.Photo;
import photosalvage.entity.PhotoFilter;
import photosalvage.entity.PhotoRepository;
import photosalvage.entity.PhotoSize;
import photosalvage.entity.PhotoStatus;
import photosalvage.entity.Place;
import photosalvage.entity.PlaceRepository;
import photosalvage.entity.User;
import photosalvage.rs.BaseResource;

import com.google.common.collect.ImmutableMap;
import com.sun.jersey.api.NotFoundException;
import com.sun.jersey.api.view.Viewable;
import com.sun.jersey.core.header.ContentDisposition;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataParam;

/**
 * @author shumpei
 *
 */
@Path("/photos")
public class Photos extends BaseResource {
	private static final Logger logger = LoggerFactory.getLogger(Photos.class);
	
	@Inject
	PlaceRepository placeRepository;
	
	@Inject
	OwnerRepository ownerRepository;

	@Inject
	AlbumRepository albumRepository;
	
	@Inject
	PhotoRepository photoRepository;
	
	
	@GET
	@Path("/{id}")
	@Produces("image/jpeg")
	public Response getImage(
			@PathParam("id") Long id,
			@QueryParam("size") String sizeName,
			@DefaultValue("false") @QueryParam("retouched") boolean retouched,
			@DefaultValue("false") @QueryParam("download") boolean download) {
		Photo photo = photoRepository.find(id);
		if (retouched && photo.getStatus() != PhotoStatus.RETOUCHED) {
			throw new NotFoundException();
		}
		PhotoSize size = PhotoSize.forName(sizeName);
		String fileName = retouched ? photo.getRetouched().getFileName() : photo.getOriginal().getFileName();
		InputStream data =
			new BufferedInputStream(photoRepository.loadPhotoData(fileName, size));
		
		ResponseBuilder rb = Response.ok(data);
		if (download) {
			rb.header("Content-Disposition", "attachment;filename=" + fileName);
		}
		return rb.build();
	}
	
	@GET
	@Path("/retouchingByMe.html")
	@Produces("text/html")
	public Response listRetouchingByMePage() {
		request.setAttribute("RETOUCHING_BY_ME", true);
		return listPage(1, 20, null, null, null,"0", "RETOUCHING_BY_ME");
	}
	@GET
	@Path("/list.html")
	@Produces("text/html")
	public Response listPage(
			@DefaultValue("1") @QueryParam("page") Integer page,
			@DefaultValue("20") @QueryParam("limit") Integer itemsPerPage,
			@QueryParam("place") String placeIdStr,
			@QueryParam("owner") String ownerIdStr,
			@QueryParam("album") String albumIdStr,
			@DefaultValue("0") @QueryParam("order") String orderStr,
			@DefaultValue("") @QueryParam("status") String status) {
		if (page <= 0) {
			throw new WebApplicationException(404);
		}
		Long placeId = StringUtils.isEmpty(placeIdStr) ? null : Long.valueOf(placeIdStr);
		Long ownerId = StringUtils.isEmpty(ownerIdStr) ? null : Long.valueOf(ownerIdStr);
		Long albumId = StringUtils.isEmpty(albumIdStr) ? null : Long.valueOf(albumIdStr);
		Long orderVal = Long.valueOf(orderStr);
		
		int startIndex = (page - 1) * itemsPerPage;
		PhotoFilter filter = new PhotoFilter();
		filter.setPlaceId(placeId);
		filter.setOwnerId(ownerId);
		filter.setAlbumId(albumId);
		filter.setStartIndex(startIndex);
		filter.setItemsPerPage(itemsPerPage);
		filter.setOrder(orderVal);
		
		List<String> queryParams = new ArrayList<String>();
		queryParams.add("order=" + orderVal);
		if (StringUtils.isNotEmpty(status)) {
			if (status.equals("RETOUCHING_BY_ME")) {
				filter.setRetoucherId(getCurrentUser().getId());
				filter.setStatus(PhotoStatus.RETOUCHING);
			} else if (status.equals("RETOUCHED_BY_ME")) {
				filter.setRetoucherId(getCurrentUser().getId());
				filter.setStatus(PhotoStatus.RETOUCHED);
			} else {
				filter.setStatus(PhotoStatus.valueOf(status));
			}
			queryParams.add("status=" + response.encodeURL(status));
		}
		PagedList<Photo> photos = photoRepository.filter(filter);
		int maxPage = (int) Math.ceil(photos.getTotalResults() / itemsPerPage) + 1;
		
		List<Place> places = placeRepository.findAll();
		List<Owner> owners = null;
		List<Album> albums = null;

		if (placeId != null) {
			queryParams.add("place=" + placeId);
			owners = ownerRepository.findByPlace(placeId);
		}
		if (ownerId != null) {
			queryParams.add("owner=" + ownerId);
			albums = albumRepository.findByOwner(ownerId);
		}
		if (albumId != null) {
			queryParams.add("album=" + albumId);
		}
		
		String param = StringUtils.join(queryParams, "&");

		Map<String, Object> context = new HashMap<String, Object>();
		context.put("user", getCurrentUser());
		context.put("photos", photos);
		context.put("limit", itemsPerPage);
		context.put("totalResults", photos.getTotalResults());
		context.put("startIndex", photos.getStartIndex());
		context.put("queryParam", param);
		context.put("currentPage", page);
		context.put("maxPage", maxPage);
		context.put("places", places);
		context.put("owners", owners);
		context.put("albums", albums);
		
		
		context.put("selectedPlaceId", placeId);
		context.put("selectedOwnerId", ownerId);
		context.put("selectedAlbumId", albumId);
		context.put("status", status);
		context.put("order", orderVal);
		return Response.ok(
				new Viewable(
						"/pc/photos/list.jsp",
						context))
						.build();
	}
	@GET
	@Path("/upload.html")
	@Produces("text/html")
	public Response uploadPage() {
		return Response.ok(new Viewable("/pc/photos/upload.jsp")).build();
	}
	@POST
	@Path("/delete")
	public Response delete(
			@DefaultValue("1") @FormParam("page") Integer page,
			@DefaultValue("20") @FormParam("limit") Integer itemsPerPage,
			@FormParam("place") String placeIdStr,
			@FormParam("owner") String ownerIdStr,
			@FormParam("album") String albumIdStr,
			@DefaultValue("") @QueryParam("status") String status,
			@FormParam("photoIds") List<Long> photoIds) {
		photoRepository.delete(photoIds);

		return listPage(page, itemsPerPage, placeIdStr, ownerIdStr, albumIdStr,"0", status);
	}
	@POST
	@Path("/upload")
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	public Response upload(@FormDataParam("photo") final List<FormDataBodyPart> photoFields) throws IOException {
		// アップロードされなかった場合は元のページに戻す
		if (photoFields.size() == 0) {
			saveErrors("ファイルが指定されていません。");
			return uploadPage();
		}
		// 一件のみのアップロードの場合は、すぐさま処理して、詳細画面に遷移
		if (photoFields.size() == 1) {
			FormDataBodyPart photoField = photoFields.get(0);
			ContentDisposition disposition = photoField.getContentDisposition();
			String fileName = disposition.getFileName();
			if (!checkPhotoFileName(fileName)) {
				return uploadPage();
			}
			InputStream data = photoField.getEntityAs(InputStream.class);
			logger.info("アップロードされたファイル{}を処理します...", fileName);
			try {
				Photo photo = photoRepository.saveUploadedPhoto(fileName, data, getCurrentUser().getId());
				logger.info("ファイル[" + fileName + "]の処理が完了しました。ID:{}", photo.getId());
				URI nextUri =
					UriBuilder.fromResource(Photos.class)
					.path(Photos.class, "detailPage")
					.queryParam("id", photo.getId())
					.build();
				return Response.seeOther(nextUri).build();
			} catch (ValidationException e) {
				saveErrors(e.getMessage());
				return uploadPage();
			}
		}
		final Long userId = getCurrentUser().getId();
		for (final FormDataBodyPart photoField : photoFields) {
			ContentDisposition disposition = photoField.getContentDisposition();
			String fileName = disposition.getFileName();
			if (!checkPhotoFileName(fileName)) {
				continue;
			}
			try {
				logger.info("アップロードされたファイル[{}]を処理します...", fileName);
				InputStream data = photoField.getEntityAs(InputStream.class);
				Photo photo = photoRepository.saveUploadedPhoto(fileName, data, userId);
				logger.info("ファイル[{}]の処理が完了しました。ID:{}", fileName, photo.getId());
			} catch (ValidationException e) {
				saveErrors(e.getMessage());
			} catch (Exception e) {
				logger.error("ファイル[" + fileName + "]の処理中にエラーが発生しました。", e);
			}
		}
		if (hasErrors()) {
			saveErrors("以上のエラーにより、いくつかのファイルは処理に失敗しました。");
		}
		return Response.ok(new Viewable("/pc/photos/uploaded.jsp")).build();
	}
	
	@POST
	@Path("/{id}/startRetouch")
	@Produces("image/jpeg")
	public Response startRetouch(@PathParam("id") Long photoId) {
		User user = getCurrentUser();
		Photo photo = photoRepository.startRetouch(photoId, user);
		logger.info("ユーザ(ID:[{}], Email:[{}])が、画像(ID:[{}],ファイル名:[{}])のレタッチを開始しました。", new Object[]{ user.getId(), user.getEmail(), photo.getId(), photo.getOriginal().getFileName()});
		InputStream data = photoRepository.loadPhotoData(photo.getOriginal().getFileName(), PhotoSize.ORIGINAL);
		return Response.ok(data).header("Content-Disposition", "attachment;filename=" + photo.getOriginal().getFileName()).build();
	}
	
	@POST
	@Path("/{id}/endRetouch")
	@Produces("text/html")
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	public Response endRetouch(
			@PathParam("id") Long photoId,
			@FormDataParam("photo") InputStream data,
			@FormDataParam("photo") FormDataContentDisposition disposition) throws IOException {
		if (StringUtils.isEmpty(disposition.getFileName())) {
			saveErrors("ファイルが選択されていません。");
			return detailPage(photoId);
		}
		String fileName = disposition.getFileName();
		try {
			User user = getCurrentUser();
			Photo photo = photoRepository.endRetouch(photoId, fileName, user, data);
			logger.info("ユーザ(ID:[{}], Email:[{}])が、画像(ID:[{}],ファイル名:[{}])のレタッチを完了しました。", new Object[]{ user.getId(), user.getEmail(), photo.getId(), photo.getOriginal().getFileName()});
			URI nextUri =
				UriBuilder.fromResource(Photos.class)
				.path(Photos.class, "detailPage")
				.queryParam("id", photo.getId())
				.build();
			return Response.seeOther(nextUri).build();
		} catch (ValidationException e){
			saveErrors(e.getMessage());
			return detailPage(photoId);
		}
	}
	@GET
	@Path("/detail.html")
	@Produces("text/html")
	public Response detailPage(@QueryParam("id") Long photoId) {
		Photo photo = photoRepository.find(photoId);
		return Response.ok(
				new Viewable(
						"/pc/photos/detail.jsp",
						ImmutableMap.of(
								"photo", photo,
								"resource", this)))
						.build();
	}
	
	private boolean checkPhotoFileName(String fileName) {
		boolean valid = PhotoSalvageConstants.PHOTO_FILE_NAME_PATTERN.matcher(fileName).matches();
		if (!valid) {
			saveErrors(fileName + "は有効なファイル名ではありません。正しいファイル名は「1_SM01_1_002_n.jpg」の形式に従う必要があります。");
		}
		return valid;
	}
}
